#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QMetaEnum>//OnActionTriggter中字符串转枚举类型
#include<QFileDialog>//OnNewFile中获取当前的文件和保存路径
#include<QFile>//Qt中专门用来处理文件操作的类
#include<QMessageBox>//弹提示框；LoadFile,下载文件中如果文件打开失败，需要一个提示弹窗
#include<QTextStream>//文本流；LoadFile，下载文件中如果文件打开成功，就需要读文件，采用流操作
#include<QSaveFile>//Qt中专门提供了这个类用于保存文件
//————————

#include <QColorDialog>// 颜色对话框相关
//打印和打印预览相关头文件
#include <QPrinter>
#include <QPrintDialog>
#include <QPrintPreviewDialog>


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //连接信号与槽
    connect(ui->file_menu,&QMenu::triggered,this,&MainWindow::OnActionTriggter);
    connect(ui->edit_menu,&QMenu::triggered,this,&MainWindow::OnActionTriggter);
    connect(ui->form_menu,&QMenu::triggered,this,&MainWindow::OnActionTriggter);
    connect(ui->win_menu,&QMenu::triggered,this,&MainWindow::OnActionTriggter);
    connect(ui->help_menu,&QMenu::triggered,this,&MainWindow::OnActionTriggter);

}

MainWindow::~MainWindow()
{
    delete ui;
}

//槽函数的前提：头文件中要有public slots:... ...
void MainWindow::OnActionTriggter(QAction *act)//点击菜单之后要干什么？
{
    //1\很多个菜单项，不知道act点击的是哪一个，因此要为他们做一个标识
    //2\用枚举来做标识，给菜单项附上枚举值，点击时获取枚举值，判断该按钮存取的枚举值是多少，通过switch语句来匹配
    //3\到头文件中声明一个枚举

    //1、先包含一个头文件QMetaEnum
    //2、定义对象(获取对象)
    QMetaEnum me=QMetaEnum::fromType<ActKind::ActionKind>();//成员函数fromType(),其类型是ActKind里的ActionKind
    //3、定义一个bool类型的ok
    bool ok;
    //4、将菜单项上的文本转换成枚举--使用函数keyToValue，获取当前获取到的菜单文本text()，通过函数toStdString().c_str()转换成字符串
    int k = me.keyToValue(act->text().toStdString().c_str(),&ok);//把ok传进去，判断转换是否成功；接收其返回值
    /*转换失败，直接return*/
    if(!ok)
        return;



    /*转换成功，继续操作switch*/
    switch(static_cast<ActKind::ActionKind>(k)){//通过静态类型转换，将int转换成枚举类型;将k转换一下
    //转换完毕

    //(1)新建文件
    case ActKind::ActionKind::NewFile:
        //（1）先要定义一个参数：头文件--slots处:OnNewFile
        //（2）调用一下OnNewFile函数
        OnNewFile();
        break;
    //(2)打开文件
    case ActKind::ActionKind::OpenFile:
        OnOpenFile();
        break;
    //(3)保存文件
    case ActKind::ActionKind::SaveFile:
        OnSave();//（从MaybeSave()中弹窗是否要保存引出来的）此处也用到要保存文件，顺便调用该函数
        break;
    //(4)另存文件
    case ActKind::ActionKind::SaveAsFile:
        OnSaveAs();//（从OnSave()中如果当前文件没有路径需要另存为引出来的）此处也用到要另保存文件，顺便调用该函数
        break;
    //(7)退出系统
    case ActKind::ActionKind::Quit:
        QApplication::quit();//QApplication里有quit()函数可以推出程序
        break;

    //<1>撤销
    case ActKind::ActionKind::Undo:
        ui->textEdit->undo();//用ui->textEdit里的undo()
        break;
    //<2>剪切
    case ActKind::ActionKind::Cut:
        ui->textEdit->cut();
        break;
    //<3>复制
    case ActKind::ActionKind::Copy:
        ui->textEdit->copy();
        break;
    //<4>粘贴
    case ActKind::ActionKind::Paste:
        ui->textEdit->paste();
        break;
    //<5>全选
    case ActKind::ActionKind::SelectAll:
        ui->textEdit->selectAll();
        break;

    /*————————*/

    //(5)打印文件
    case ActKind::ActionKind::PrintFile:
        OnPrint();
        break;
    //(6)预览打印文件
    case ActKind::ActionKind::PrintPreviewFile:
        OnPrintPreview();
        break;

    //[font-1]加粗
    case ActKind::ActionKind::Bold:
        OnBold();
        break;
    //[font-2]倾斜
    case ActKind::ActionKind::Italic:
        OnItalic();
        break;
    //[font-3]下划线
    case ActKind::ActionKind::UnderLine:
        OnUnderLine();
        break;
    //[paragraph-1]左对齐
    case ActKind::ActionKind::LeftAlignment:
        OnLeftAlignment();
        break;
    //[paragraph-2]居中
    case ActKind::ActionKind::CenterAlignment:
        OnCenterAlignment();
        break;
    //[paragraph-3]右对齐
    case ActKind::ActionKind::RightAlignment:
        OnRightAlignment();
        break;
    //[paragraph-4]两端对齐
    case ActKind::ActionKind::JustifiedAlignment:
        OnJustifiedAlignment();
        break;
    //[3-1]颜色
    case ActKind::ActionKind::Color:
        onFontColor();
        break;

    //【1-1】关闭当前文件
    case ActKind::ActionKind::Close:
        break;
    //【1-2】关闭所有窗口
    case ActKind::ActionKind::CloseAllWindows:
        break;
    //【2-1】平铺
    case ActKind::ActionKind::Tile:
        break;
    //【2-2】层叠
    case ActKind::ActionKind::Cascade:
        break;
    //【3-1】下一个
    case ActKind::ActionKind::Next:
        break;
    //【3-2】前一个
    case ActKind::ActionKind::Previous:
        break;

    //{1}关于
    case ActKind::ActionKind::About:
        OnAbout();
        break;
    //{2}关于Qt
    case ActKind::ActionKind::AboutQt:
        OnAboutQt();
        break;

    }
}
//新建文件
void MainWindow::OnNewFile()
{
    //这里考虑到，新建一个文件（未命名），点击“保存”触发的是“另存为”，因为是新建的文件，所以没有指定其保存的位置
    //因此需要判断这个文件有没有保存过：先去写MaybeSave()函数

    //1、先判断这个文件有没有保存
    if(MaybeSave())//如果不需要保存
    {
        //直接把原有的内容清空
        ui->textEdit->clear();
        //路径也置为空
        cur_file=QString();
        //手动将文本内容变成没有编辑
        ui->textEdit->document()->setModified(false);
    }
    //2、需要保存时，在判断时
}
//（一）打开文件（是否需要保存当前文件）
void MainWindow::OnOpenFile()
{
    //1、判断当前文件有没有保存
    //2、因此需要添加一个辅助函数，从头文件开始private:MaybeSave()
    if(MaybeSave())
    {
        //(1)如果需要保存，就需要获取一下当前的文件和保存路径，通过QFileDialog（#include）来
        //(2)定义一下对象，获取绝对路径;QFileDialog里面有一个静态成员getOpenFileName()
        QString fileName=QFileDialog::getOpenFileName(this);//传递一个父对象this;这样文件对话框就创立好了
        //(3)判断我们获取到的fileName不为空，就需要将文件的内容加载到文件夹里面
        if(!fileName.isEmpty())
            //要打开文件，去获取要打开的文件名，如果打开的文件名不为空，就将文件的内容加载到文件夹里面
            //如果不需要保存，点打开文件的时候，弹框，点了“取消”，那么什么都不用做
            //因此需要加载文件，先写一个辅助函数 ，从头文件开始
            //<1>直接调用
            LoadFile(fileName);
    }

    //运行后测试步骤：1、随意打一些数字，点击SaveFile保存一下,由于这是一个新建的文本，因此打开的是“另存为”，没问题
    //2、不动文本内容，点击OpenFile打开一下刚才保存的文件，结果发现弹窗提示“当前文件未保存”是否需要保存的情况
}
//（四）文件保存
bool MainWindow::OnSave()
{
    //保存这里考虑到，新建一个文件（未命名），点击“保存”触发的是“另存为”，因为是新建的文件，所以没有指定其保存的位置
    //因此需要判断这个文件有没有保存过
    //（1）添加一个QString到头文件的private里:QString cur_file;用于保存当前文件的路径位置
    if(cur_file.isEmpty())
        //如果当前位置为空，需要对它进行“另存为”的操作
        //因此也需要判断其“另存为”的操作有没有成功，所以到头文件中添加一个新的判断函数到slots:OnSaveAs()
        //1、如果为空，调用我们的OnSaveAs()
        return OnSaveAs();
    else//2、否则，当前文件在文件夹中已有路径，要将当前文件写入文件夹，需要一个保存函数，来一个辅助函数，从头文件开始private:中SaveFile()
        return SaveFile(cur_file);
}
//（四）-<1>另存为（即文件夹中没有路径的文件保存成功）
bool MainWindow::OnSaveAs()
{
    //1、需要获取你要保存的文件的一个路径位置，通过对话框，头文件（#include<QFileDialog>）
    QFileDialog dialog(this);
    //设定对话框的模式，将其设定成模态的，需要 选择 文件
    dialog.setWindowModality(Qt::WindowModal);
    dialog.setAcceptMode(QFileDialog::AcceptSave);//设置按钮
    //运行起来
    if(dialog.exec()!=QDialog::Accepted)//'不等于接受'说明接收了“取消”，说明当前文件不需要保存
        return false;
    //接受则需要保存
    return SaveFile(dialog.selectedFiles().first());//获取到先前选择的路径（.selectedFiles()），dialog.selectedFiles()返回的是一个列表，选取其第一条（.first()）
}
//加粗
void MainWindow::OnBold()
{
    //1、获取光标获取的当前文本的对象，创建文本格式对象，用于设置字体样式
    QTextCharFormat fmt;
    //2、 根据boldcheck状态设置字体粗细：
    // 若boldcheck为true，设置为粗体(QFont::Bold)
    // 若boldcheck为false，设置为正常(QFont::Normal)
    fmt.setFontWeight(boldcheck ? QFont::Bold : QFont::Normal);
    //3、 将设置好的字体格式应用到当前文本选区内
    // （注：mergeFormat()需自行实现，通常通过QTextCursor操作）
    mergeFormat(fmt);
    //4、 切换粗体状态标志，实现"开关"效果
    boldcheck = !boldcheck;
    //5、 输出调试信息，记录本次粗体切换操作及最终状态
    qDebug() << "boldcheck triggered, state:" << boldcheck;
}

//设置光标的选区，使格式作用于选区内的字符
void MainWindow::mergeFormat(QTextCharFormat fmt)//QTextCharFormat 对象，包含字体、颜色、样式等文本格式信息
{                                                   //将指定的字符格式 fmt 合并到当前选中的文本上。
    //1、获取当前文本光标~QTextCursor：
    QTextCursor cursor = ui->textEdit->textCursor();
    if(!cursor.hasSelection()){//如果当前没有选中文本
        cursor.select(QTextCursor::WordUnderCursor);//自动选中光标所在位置的单词（QTextCursor::WordUnderCursor），确保至少有一个单词被处理。
                                                    //WordUnderCursor 是 QTextCursor 类中的一个枚举值，用于指定文本选择模式。它表示选中光标所在位置的完整单词
    }
    cursor.mergeCharFormat(fmt);//只修改 fmt 中显式设置的属性（如字体加粗），未设置的属性保持不变（如原有字体大小）
}
//倾斜
void MainWindow::OnItalic()
{
    //1、获取光标获取的当前文本的对象，创建文本格式对象，用于设置字体样式
    QTextCharFormat fmt;
    //2、 根据Italiccheck状态设置斜体格式：
    // 若Italiccheck为true，启用斜体；否则禁用斜体
    fmt.setFontItalic(Italiccheck ? true : false);
    //3、 将设置好的字体格式应用到当前文本选区内
    // （注：mergeFormat()需自行实现，通常通过QTextCursor操作）
    mergeFormat(fmt);
    //4、 切换斜体状态标志位，实现"开关"切换效果
    Italiccheck = !Italiccheck;
    //5、 输出调试日志，记录斜体状态变更
    qDebug() << "Italiccheck triggered, state:" << Italiccheck;
}
//下划线
void MainWindow::OnUnderLine()
{
    //1、获取光标获取的当前文本的对象，创建文本格式对象，用于设置字体样式
    QTextCharFormat fmt;
    //2、 根据UnderLinecheck状态设置斜体格式：
    // 若UnderLinecheck为true，启用下划线；否则禁用下划线
    fmt.setFontUnderline(UnderLinecheck ? true : false);
    //3、 将设置好的字体格式应用到当前文本选区内
    // （注：mergeFormat()需自行实现，通常通过QTextCursor操作）
    mergeFormat(fmt);
    //4、 切换下划线状态标志位，实现"开关"切换效果
    UnderLinecheck = !UnderLinecheck;
    //5、 输出调试日志，记录下划线状态变更
    qDebug() << "OnUnderLine triggered, state:" << UnderLinecheck;
}

//左对齐
void MainWindow::OnLeftAlignment()
{
    //1、获取文本格式对象
    QTextBlockFormat format;
    format.setAlignment(Qt::AlignLeft);
    mergeTextFormat(format);

    //2、 输出调试日志，判断函数是否调用
    qDebug() << "OnLeftAlignment";
}
//右对齐
void MainWindow::OnRightAlignment()
{
    //1、获取文本格式对象
    QTextBlockFormat format;
    format.setAlignment(Qt::AlignRight | Qt::AlignCenter);
    mergeTextFormat(format);
    //2、 输出调试日志，判断函数是否调用
    qDebug() << "OnRightAlignment";
}

//居中
void MainWindow::OnCenterAlignment()
{

    //1、获取文本格式对象
    QTextBlockFormat format;
    format.setAlignment(Qt::AlignCenter);
    mergeTextFormat(format);
    //2、 输出调试日志，判断函数是否调用
    qDebug() << "OnCenterAlignment";

}
//当前文本编辑的光标
void MainWindow::mergeTextFormat(const QTextBlockFormat& format)
{
    QTextCursor cursor = ui->textEdit->textCursor();
    cursor.setBlockFormat(format);
    ui->textEdit->setTextCursor(cursor);
}
//两端对齐
void MainWindow::OnJustifiedAlignment()
{
    //1、获取文本格式对象
    QTextBlockFormat format;
    format.setAlignment(Qt::AlignJustify);
    mergeTextFormat(format);
    //2、 输出调试日志，判断函数是否调用
    qDebug() << "OnJustifiedAlignment";

}
//（二）辅助函数MaybeSave()用于判断当前文件是否需要保存
bool MainWindow::MaybeSave()//MaybeSave()————（可能需要保存）
{
    //1、判断当前文本有没有修改:获取文本用document()，有没有修改用isModified()
    if(!ui->textEdit->document()->isModified())//（1-1）如果没有修改
        return true;//不保存
    //（1-2）有修改就弹框
        //<1-2-1>获取对象并增加三个按钮“保存”“不保存”“取消”；qApp->applicationName()--获取到当前应用程序的名称
    const QMessageBox::StandardButton btn=QMessageBox::warning(this,qApp->applicationName(),"文件没有保存",QMessageBox::Save|QMessageBox::Discard|QMessageBox::Cancel);
        //<1-2-2>通过switch语句判断点的是哪一个按钮
    switch (btn){
    case QMessageBox::Save://如果是保存，那么要先定义一个保存的函数，到头文件(slots:OnSave())
        return OnSave();
        break;
    case QMessageBox::Cancel://如果是取消，那么直接返回false
        return false;//不做任何操作
    }
    return true;//如果是不保存，那么直接返回true
}
//（三）加载文件（打开文件）--需要用到Qt里的文件操作：QFile类（#include）
void MainWindow::LoadFile(const QString &path)
{
    //1、定义完头文件之后（#include<QFile>）
    //2、定义类对象
    QFile file(path);//路径传进来
    //3、打开文件,file类里的open()函数打开，打开方式是只读,并且默认是文本形式打开,可能是乱码，所有前面要加一个非（!）
    if(!file.open(QFile::ReadOnly|QFile::Text)){
        //（1）如果打开失败，要给一个提示；用到#include<QMessageBox>--弹窗提示
        QMessageBox::warning(this,qApp->applicationName(),"无法打开文件");//qApp->applicationName()--获取到当前应用程序的名称
        return;
    }
    //（2）如果打开成功，就需要读文件，采用流操作，用到#include<QTextStream>--文本流
        //定义对象
    QTextStream in(&file);//把文件传进来
    //文件传进来之后，将它直接显示到我们界面上，在“设计”中得添加一个文本，随便一个垂直布局铺满
    //再。。。注意：以上设计完成后要先“构建”一下（即保存一下），才能继续
    ui->textEdit->setPlainText(in.readAll());//设置一下文本（setPlainText()），把文件里的数据读出来（in.readAll()），将它显示出来

    cur_file=path;//更新文件路径

    //（<——OpenFile——>）之所以会再一次弹窗提示是否要保存，是因为文本框被修改了，所以这里要设置一下，把文本框变成未修改
    ui->textEdit->document()->setModified(false);//用ui->textEdit里的setModified()

    //提示--用statusBar()
    statusBar()->showMessage("文件打开成功",2000);//显示时长2秒


    //在添加完ui->textEdit->document()->setModified(false);之后问题依然没有解决，还是弹窗说文件没有保存
    //那么看到SaveFile()函数，同样添加ui->textEdit->document()->setModified(false);
}
//（四）-<2>文件夹中已有路径的文件保存
bool MainWindow::SaveFile(const QString &path)
{
    //1、Qt中专门提供了这么一个类（#include<QSaveFile>）来保存文件
    //2、创建对象
    QSaveFile file(path);

    if(file.open(QFile::WriteOnly|QFile::Text))//打开文件
    {
        //（）打开成功：
        //3、写入
        QTextStream out(&file);//获取流对象，把file传进来
        //往流里面写入数据(把内容写入文件)
        out<<ui->textEdit->toPlainText();//ui->textEdit是文本，用toPlainText()来获取文本
        //4、file提交（.commit()）来判断是否保存成功
        if(!file.commit())
        {
            //（1）保存失败：提示框提示
            QMessageBox::warning(this,qApp->applicationName(),"文件保存失败！");
            return false;
        }
    }
    else//打开失败
        return false;
    //（2）保存成功：
    //5、更新文件路径
    cur_file=path;

    ui->textEdit->document()->setModified(false);

    //6、提示--用statusBar()
    statusBar()->showMessage("文件保存成功!",2000);//提示两秒钟
    return true;
}

//字体类型选择
void MainWindow::on_fontComboBox_currentFontChanged(const QFont &f)
{
    //1、 检查是否有选中文本
    if (ui->textEdit->textCursor().hasSelection()) {
        //2、获取当前文本光标（表示当前编辑位置和选中范围
        QTextCursor cursor = ui->textEdit->textCursor();
        //3、创建文本格式对象，用于设置文本属性
        QTextCharFormat format;
        //4、设置字体族（如 "Arial", "宋体" 等）
        format.setFont(f);
        // 方法1：通过光标应用格式 - 仅影响选中的文本部分
        cursor.mergeCharFormat(format);
        // 方法2：通过文本编辑框应用格式 - 影响后续输入的文本
        ui->textEdit->mergeCurrentCharFormat(format);
    }

}
//字体大小
void MainWindow::on_sizeComboBox_currentTextChanged(const QString &arg1)
{
    //1、 检查文本编辑区中是否有选中的文本
    if (ui->textEdit->textCursor().hasSelection()) {
        // 2、获取当前文本编辑区的光标对象，QTextCursor包含了当前文本的选择范围和编辑位置信息
        QTextCursor cursor = ui->textEdit->textCursor();
        //3、创建文本格式对象，用于存储和设置文本的字体大小属性
        QTextCharFormat format;
        //4、 将下拉框选中的字符串字号（如"12"）转换为double类型，并设置到格式对象中
        format.setFontPointSize(arg1.toDouble());
        //5、应用格式到选中的文本：通过光标对象仅修改当前选中的文本部分
        // mergeCharFormat()会合并现有格式与新格式（仅修改指定的属性，保留其他属性）
        cursor.mergeCharFormat(format);
        // 同时将新字号设置为当前光标位置的默认格式
        // 确保用户在当前位置继续输入时，新文本会使用该字号
        ui->textEdit->mergeCurrentCharFormat(format);
    }
}

//字体颜色
void MainWindow::onFontColor()
{
    // 打开颜色选择对话框
    // 参数说明：
    // - 第一个参数：默认选中的颜色（此处为黑色 Qt::black）
    // - 第二个参数：父窗口指针（确保对话框模态显示在当前窗口上）
    // - 第三个参数：对话框标题
    QColor color = QColorDialog::getColor(Qt::black, this, "选择文本颜色");

    // 检查两个条件：
    // 1. 用户选择的颜色是否有效（未点击"取消"按钮）
    // 2. 文本编辑区是否有选中的文本
    if (color.isValid() && ui->textEdit->textCursor().hasSelection()) {
        // 创建文本格式对象，用于设置文本颜色属性
        QTextCharFormat format;

        // 设置文本的前景色（即文字颜色）为用户选择的颜色
        format.setForeground(color);

        // 将颜色格式应用到选中文本：
        // mergeCurrentCharFormat 会保留文本原有的其他格式（如字体、字号），仅更新颜色
        ui->textEdit->mergeCurrentCharFormat(format);
    }

}
//打印
void MainWindow::OnPrint()
{
    //1、创建打印机对象
    QPrinter printer;
    //2、创建打印对话框
    QPrintDialog dlg(&printer,this);
    //3、光标选中了文本内容
    if(ui->textEdit->textCursor().hasSelection())
        dlg.setOption(QAbstractPrintDialog::PrintSelection,true);
    //4、光标没有选中，打印整个
    if (dlg.exec() == QDialog::Accepted)
    {
        ui->textEdit->print(&printer);
    }
}
//打印预览
void MainWindow::OnPrintPreview()
{
    //1、创建打印机对象
    QPrinter printer;
    QPrintPreviewDialog preview(&printer,this);
    connect(&preview,&QPrintPreviewDialog::paintRequested,this,&MainWindow::OnPrintPreviewHelp);
    preview.exec();
}
void MainWindow::OnPrintPreviewHelp(QPrinter *printer)
{
    ui->textEdit->print(printer);
}
void MainWindow::OnAbout()
{
    QMessageBox::about(this, tr("关于应用"),
                       tr("<h3>应用名称 Office办公自动化文本编辑软件</h3>"
                          "<p>版权所有 (C) 2025</p>"
                          "<p>这是一个使用Qt框架开发的示例应用程序。</p>"
                          "<p>更多信息请访问 <a href='https://example.com'>我们的网站</a></p>"));
}

void MainWindow::OnAboutQt()
{
    // 可选：添加自定义代码，如日志记录
    qDebug() << "用户请求查看Qt版本信息";

    // 调用Qt内置的关于Qt对话框
    QApplication::aboutQt();
}




